import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Autocomplete implements IAutocomplete {
    private Node root;

    public Autocomplete() {
        root = new Node();
    }

    /**
     * add a word with weight to trie.
     * if word has any non a-z char, do nothing.
     */
    @Override
    public void addWord(String word, long weight) {
        // validate input word
        if (word == null || !word.matches("^[a-z]+$")) {
            return;
        }

        Node current = root;
        current.setPrefixes(current.getPrefixes() + 1);

        for (char c : word.toCharArray()) {
            int index = c - 'a';
            Node[] children = current.getReferences();

            // create new branch if needed
            if (children[index] == null) {
                children[index] = new Node();
            }

            current = children[index];
            // track prefix count
            current.setPrefixes(current.getPrefixes() + 1);
        }

        // mark as complete word
        current.setWords(1);
        current.setTerm(new Term(word, weight));
    }

    /**
     * build trie from file.
     * each line: weight<TAB>word.
     * k is max suggestion count (for gui, not stored).
     */
    @Override
    public Node buildTrie(String filename, int k) {
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            reader.lines()
                  .map(line -> line.split("\t"))
                  .filter(parts -> parts.length >= 2)
                  .forEach(parts -> {
                      try {
                          // parse line: weight and word
                          long weight = Long.parseLong(parts[0].trim());
                          String word = parts[1].trim().toLowerCase();
                          addWord(word, weight);
                      } catch (NumberFormatException ignored) {}
                  });
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
        return root;
    }

    /**
     * return sub trie for a given prefix.
     * if not found, return null.
     */
    @Override
    public Node getSubTrie(String prefix) {
        // validate input
        if (prefix == null) {
            return null;
        }

        prefix = prefix.toLowerCase();
        Node current = root;

        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            // check valid character range
            if (index < 0 || index >= 26) {
                return null;
            }

            Node[] children = current.getReferences();
            // stop if path doesn't exist
            if (children[index] == null) {
                return null;
            }
            current = children[index];
        }

        return current;
    }

    /**
     * return number of words with given prefix.
     */
    @Override
    public int countPrefixes(String prefix) {
        // find subtrie and count prefixes
        Node subTrie = getSubTrie(prefix);
        return subTrie != null ? subTrie.getPrefixes() : 0;
    }

    /**
     * helper: dfs traverse sub trie and collect full word terms.
     * add a copy of term to list.
     */
    private void collectTerms(Node node, List<ITerm> list) {
        if (node == null) {
            return;
        }

        // collect complete words
        if (node.getWords() == 1 && node.getTerm() != null) {
            Term original = node.getTerm();
            list.add(new Term(original.getTerm(), original.getWeight()));
        }

        // recursive traversal
        for (Node child : node.getReferences()) {
            if (child != null) {
                collectTerms(child, list);
            }
        }
    }

    /**
     * return list of suggestions for a given prefix.
     * term objects are copies.
     */
    @Override
    public List<ITerm> getSuggestions(String prefix) {
        // init empty list
        List<ITerm> suggestions = new ArrayList<>();

        // validate input
        if (prefix == null) {
            return suggestions;
        }

        // find subtrie for prefix
        Node subTrie = getSubTrie(prefix);
        if (subTrie != null) {
            collectTerms(subTrie, suggestions);
        }

        return suggestions;
    }
}
